#include "Client.h"
#include "Location.h"
#include <libge/libge.h>

#define lerp(t, a, b) ( a + t * (b - a) )
#define N_SAMPLES 16

float kernel[N_SAMPLES*3] = { 0.0 };
float sceneProj[16] = { 0.0 };
ge_Image* noise = NULL;

ThreadRender::ThreadRender(Client* cli) : ClientThread(cli, "ThreadRender") {
	client = cli;
	picking_block = NULL;
	picking_location = new Location();
	geClearColor(0xFFFF8080);
	cpu_time = 0;
	gpu_time = 0;
	general_time = geGetTick();

	gePrintDebug(0, "ThreadRender::ThreadRender 1\n");
	fbo = geCreateFramebuffer(geGetContext()->width, geGetContext()->height);
	gePrintDebug(0, "ThreadRender::ThreadRender 2\n");
	lightScatter = geLightScatterCreate(4, NULL, fbo->texture, fbo->depth);
	gePrintDebug(0, "ThreadRender::ThreadRender 3\n");
	lensFlare = geGfxLensFlareCreate(NULL, NULL, NULL, fbo->depth);
	gePrintDebug(0, "ThreadRender::ThreadRender 4\n");
	lensFlare->alpha = 1.0;



/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	
	int i, x, y;

	srand(time(NULL));
	for(i=0; i<N_SAMPLES; i++){
		kernel[i*3 + 0] = ((float)(rand() % 32768)) / 16384.0 - 1.0;
		kernel[i*3 + 1] = ((float)(rand() % 32768)) / 16384.0 - 1.0;
		kernel[i*3 + 2] = ((float)(rand() % 32768)) / 16384.0 - 1.0;
		//kernel[i*3 + 2] = ((float)(rand() % 32768)) / 32768.0;
		geNormalize(&kernel[i*3]);
		/*
		float f = ((float)(rand() % 32768)) / 32768.0;
		kernel[i*3 + 0] *= f;
		kernel[i*3 + 1] *= f;
		kernel[i*3 + 2] *= f;
		*/
		float scale = (float)i / (float)N_SAMPLES;
		scale = lerp(0.1f, 1.0f, scale * scale);
		kernel[i*3 + 0] *= scale;
		kernel[i*3 + 1] *= scale;
		kernel[i*3 + 2] *= scale;
	}

	noise = geCreateSurface(128, 128, 0x00000000);
	for(y=0; y<noise->height; y++){
		for(x=0; x<noise->width; x++){
			float vec[3] = { 0.0 };
			vec[0] = ((float)(rand() % 32768)) / 16384.0 - 1.0;
			vec[1] = ((float)(rand() % 32768)) / 16384.0 - 1.0;
			vec[2] = 0.0;
			geNormalize(vec);
			vec[0] = (vec[0] + 1.0) / 2.0;
			vec[1] = (vec[1] + 1.0) / 2.0;
			vec[2] = (vec[2] + 1.0) / 2.0;
		//	printf("rand : %f %f %f\n", vec[0], vec[1], vec[2]);
			noise->data[x + noise->textureWidth*y] = RGBAf(vec[0], vec[1], vec[2], 255);
		}
	}
	geUpdateImage(noise);


	fbo_normal = geCreateFramebuffer(geGetContext()->width, geGetContext()->height);
	fbo_final = geCreateFramebuffer(geGetContext()->width, geGetContext()->height);
	/*
	geTextureMode(fbo->texture, GE_NEAREST);
	geTextureMode(fbo->depth, GE_NEAREST);
	geTextureMode(fbo_normal->texture, GE_NEAREST);
	geTextureMode(fbo_normal->depth, GE_NEAREST);
	geTextureWrap(fbo->texture, GE_CLAMP_TO_EDGE, GE_CLAMP_TO_EDGE);
	geTextureWrap(fbo->depth, GE_CLAMP_TO_EDGE, GE_CLAMP_TO_EDGE);
	geTextureWrap(fbo_normal->texture, GE_CLAMP_TO_EDGE, GE_CLAMP_TO_EDGE);
	geTextureWrap(fbo_normal->depth, GE_CLAMP_TO_EDGE, GE_CLAMP_TO_EDGE);
	geTextureWrap(fbo_final->texture, GE_CLAMP_TO_EDGE, GE_CLAMP_TO_EDGE);
	geTextureWrap(fbo_final->depth, GE_CLAMP_TO_EDGE, GE_CLAMP_TO_EDGE);
	*/
	
	shader_ssao = geCreateShader();
	geShaderLoadVertexSource(shader_ssao, "shaders/ssao.vert");
	geShaderLoadFragmentSource(shader_ssao, "shaders/ssao.frag");
	geShaderUse(shader_ssao);
	geShaderUniform1i(geShaderUniformID(shader_ssao, "depthTexture"), 1);
	geShaderUniform1i(geShaderUniformID(shader_ssao, "noiseTexture"), 2);
	geShaderUniform1i(geShaderUniformID(shader_ssao, "normalTexture"), 3);
	loc_kernel = geShaderUniformID(shader_ssao, "kernelOffsets");
	loc_sceneProj = geShaderUniformID(shader_ssao, "sceneProj");
	geShaderUniform3fv(loc_kernel, N_SAMPLES, kernel);
	geShaderUse(NULL);

	shader_normal = geCreateShader();
	geShaderLoadVertexSource(shader_normal, "shaders/normal.vert");
	geShaderLoadFragmentSource(shader_normal, "shaders/normal.frag");

	shader_final1 = geCreateShader();
	geShaderLoadVertexSource(shader_final1, "shaders/ssao_final.vert");
	geShaderLoadFragmentSource(shader_final1, "shaders/ssao_final1.frag");

	shader_final2 = geCreateShader();
	geShaderLoadVertexSource(shader_final2, "shaders/ssao_final.vert");
	geShaderLoadFragmentSource(shader_final2, "shaders/ssao_final2.frag");
	geShaderUse(shader_final2);
	geShaderUniform1i(geShaderUniformID(shader_final2, "sceneTexture"), 1);
	geShaderUse(NULL);


	ssao = 0;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}

void ThreadRender::SetPickingBlock(Block* block, float x, float y, float z){
	picking_block = block;
	picking_location->setXYZ(x, y, z);
}

void ThreadRender::EnqueueChunkUpdate(Chunk* chunk){
	chunks_update.push_back(chunk);
}

void ThreadRender::RenderEntity(Entity* e){
	if(e->isLiving() && e->getType() == ENTITY_PLAYER){
		Player* p = (Player*)e;
		Location loc = e->getLocation();
	//	printf("  player '%s' : %f, %f, %f\n", p->getName(), loc.getX(), loc.getY(), loc.getZ());
		for(u32 j=0; j<client->res.entities.steve.size(); j++){
			ge_Object* obj = client->res.entities.steve.at(j);
			geObjectMatrixIdentity(obj);
			geObjectMatrixTranslate(obj, loc.getX(), loc.getY(), loc.getZ());
			geObjectMatrixRotate(obj, 0.0, 0.0, loc.getYaw() * M_PI / 180.0);
			if(!strcmp(obj->name, "steve_head")){
				geObjectMatrixRotate(obj, 0.0, 0.0, M_PI + loc.getHeadYaw() * M_PI / 180.0);
				geObjectMatrixTranslate(obj, 0.0, 0.0, 1.45);
				geObjectMatrixRotate(obj, 0.0, loc.getPitch() * M_PI / 180.0, 0.0);
			}else{
				if(!strcmp(obj->name, "steve_body")){
					geObjectMatrixTranslate(obj, 0.0, 0.0, 0.7);
				}else
				if(!strcmp(obj->name, "steve_leg1")){
					geObjectMatrixTranslate(obj, 0.0, 0.11, 0.7);
					geObjectMatrixRotate(obj, 0.0, geSin(e->getAnimationTick() * 7.0), 0.0);
				}else
				if(!strcmp(obj->name, "steve_leg2")){
					geObjectMatrixTranslate(obj, 0.0, -0.11, 0.7);
					geObjectMatrixRotate(obj, 0.0, -geSin(e->getAnimationTick() * 7.0), 0.0);
				}else
				if(!strcmp(obj->name, "steve_arm1")){
					geObjectMatrixTranslate(obj, 0.0, 0.33, 1.45);
					geObjectMatrixRotate(obj, 0.0, -geSin(e->getAnimationTick() * 7.0), 0.0);
				}else
				if(!strcmp(obj->name, "steve_arm2")){
					geObjectMatrixTranslate(obj, 0.0, -0.33, 1.45);
					geObjectMatrixRotate(obj, 0.0, geSin(e->getAnimationTick() * 7.0), 0.0);
				}
			}
			geObjectDraw(obj);
		}
		if(p != client->getPlayer()){
			float scale = 0.01 * fmaxf(1.0, fminf(3.0, 0.05 * geDistance3D(client->cam->x, client->cam->y, client->cam->z, loc.getX(), loc.getY(), loc.getZ() + 2.1)));
			geMatrixMode(GE_MATRIX_MODEL);
			geLoadIdentity();
			geTranslate(loc.getX(), loc.getY(), loc.getZ() + 2.1);
			geRotate(-M_PI / 2.0, M_PI / 2.0 - client->cam->RotH_rad, 0.0);
			geScale(scale, scale, scale);
			int w=0, h=0;
			geFontMeasureText(client->res.font, p->getName(), &w, &h);
			geTranslate(-(((float)w) / 2.0), -scale * 1000.0, 0.0);
			geUpdateMatrix();
			glFrontFace(GL_CCW);
			geTextureMode(client->res.font->texture, GE_NEAREST);
			geFontPrintScreen(0, 0, client->res.font, p->getName(), 0xFFFFFFFF);
			glFrontFace(GL_CW);
		}
	}
}

void ThreadRender::RenderWorld(){
	geCameraRotateWithMouse(client->cam, 0.25);
	geCameraLook(client->cam);
	geMatrixMode(GE_MATRIX_MODEL);
	geLoadIdentity();
	geUpdateMatrix();
	geShaderUse(client->res.shader_base);
	geTextureImage(1, client->res.cube->material.textures[1]);
	geShaderUniform1i(geShaderUniformID(client->res.shader_base, "ge_Texture1"), 1);
	geTextureImage(0, client->res.terrain);
	RenderChunks();
//	RenderPick();
	
	geRendererUse(client->res.entities.render);
	
	std::vector<Entity*>* entities = client->getWorld()->getEntities();
	for(u32 i=0; i<entities->size(); i++){
		Entity* e = entities->at(i);
		if(client->getWorld()->FindChunk(e->getLocation().getX(), e->getLocation().getY())){
			RenderEntity(e);
		}
	}
	

	//RenderEntity(client->getPlayer());
}

void ThreadRender::run(){
	float sun_matrix[16] = { 0.0 };

//	geReadKeys(client->keys);
	if(geKeysToggled(client->keys, GEK_LBUTTON)){
		this->mouseClickLeft = true;
	}
	if(geKeysToggled(client->keys, GEK_RBUTTON)){
		this->mouseClickRight = true;
	}

	if(geKeysToggled(client->keys, GEK_F11)){
		if(client->options.fullscreen){
			geFullscreen(false, -1, -1);
			client->options.fullscreen = false;
		}else{
			geFullscreen(true, -1, -1);
			client->options.fullscreen = true;
		}
	}

	if(geGetContext()->width != fbo->texture->width || geGetContext()->height != fbo->texture->height){
		geFreeFramebuffer(fbo);
		fbo = geCreateFramebuffer(geGetContext()->width, geGetContext()->height);
		lightScatter->color_buffer = fbo->texture;
		lightScatter->depth_buffer = fbo->depth;
		lensFlare->depth_buffer = fbo->depth;
	}

	for(u32 i=0; i<8 && chunks_update.size()>0; i++){
		Chunk* c = chunks_update.front();
		geRendererUpdateContext(NULL, c->getRenderer());
		geFree(c->getRenderer()->verts);
		c->getRenderer()->verts = NULL;
		c->getRenderer()->enabled = true;
		c->setVisible(true);
		c->setReserved(false);
		chunks_update.pop_front();
	}
	
	geFramebufferUse(fbo);
	geClearMode(GE_CLEAR_DEPTH_BUFFER | GE_CLEAR_COLOR_BUFFER);
	/*
	global_illum = 0.05;
	if(client->getDayTicks() >= 6000 && client->getDayTicks() <= 18000){
		global_illum = fmax(0.05, fmin(1.0, 0.25 + log(1.0 + geSin(M_PI * ((float)client->getDayTicks() - 6000.0) / 12000.0))));
	}
	*/
	global_illum = 1.0;
	geClearColor(RGBA((int)(128.0*global_illum), (int)(128.0*global_illum), (int)(255.0*global_illum), 255));
	geClearScreen();
	
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	//RenderWorld();
	memcpy(sceneProj, geGetMatrix(GE_MATRIX_PROJECTION), sizeof(float)*16);
	RenderWorld();
	RenderPick();
	geFramebufferUse(NULL);
		
	geFramebufferUse(fbo_normal);
	geForceShader(shader_normal);
	geClearScreen();
	RenderWorld();
	geForceShader(NULL);
	geFramebufferUse(NULL);
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	
	
	geMatrix44Mult(sun_matrix, geGetMatrix(GE_MATRIX_PROJECTION), geGetMatrix(GE_MATRIX_VIEW));
	ge_Translate(sun_matrix, client->res.sun->position.x, client->res.sun->position.y, client->res.sun->position.z);
	

	
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	if(ssao >= 1){
		geDrawingMode(GE_DRAWING_MODE_2D);
		geFramebufferUse(fbo_final);
		geShaderUse(shader_ssao);
		geTextureImage(3, fbo_normal->texture);
		geTextureImage(2, noise);
		geTextureImage(1, fbo->depth);
		geTextureImage(0, NULL);
		geBlitImage(0, 0, fbo->texture, 0, 0, fbo->texture->width, fbo->texture->height, GE_BLIT_VFLIP | GE_BLIT_NOALPHA);
		geFramebufferUse(NULL);
		
		if(ssao == 1){
			geFramebufferUse(fbo_normal);
			geShaderUse(shader_final1);
			geBlitImage(0, 0, fbo_final->texture, 0, 0, fbo_final->texture->width, fbo_final->texture->height, GE_BLIT_VFLIP | GE_BLIT_NOALPHA);
			geFramebufferUse(NULL);
		
			geFramebufferUse(fbo_final);
			geShaderUse(shader_final2);
			geTextureImage(1, fbo->texture);
			geTextureImage(0, NULL);
			geBlitImage(0, 0, fbo_normal->texture, 0, 0, fbo_normal->texture->width, fbo_normal->texture->height, GE_BLIT_VFLIP | GE_BLIT_NOALPHA);
			geFramebufferUse(NULL);
		}
		geShaderUse(NULL);
		lightScatter->color_buffer = fbo_final->texture;
		
	}else{
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		lightScatter->color_buffer = fbo->texture;
	}
	
	geDrawingMode(GE_DRAWING_MODE_2D | GE_DRAWING_2D_DEPTH);
	geClearMode(GE_CLEAR_DEPTH_BUFFER);
	geClearScreen();
	geShaderUse(NULL);
	RenderSun(sun_matrix);
	
	geDrawLineScreen(geGetContext()->width/2-10, geGetContext()->height/2, geGetContext()->width/2+10, geGetContext()->height/2, 0xFFFFFFFF);
	geDrawLineScreen(geGetContext()->width/2, geGetContext()->height/2-10, geGetContext()->width/2, geGetContext()->height/2+10, 0xFFFFFFFF);

	if(client->options.debug){
		geFontPrintfScreen(0, 0, client->res.font, 0xFFFFFFFF, "FPS: %d", geFps());
		geFontPrintfScreen(0, 300, client->res.font, 0xFFFFFFFF, "systicks : %.3f", ((float)geGetTick())/1000.0);
		geFontPrintfScreen(200, 0, client->res.font, 0xFFFFFFFF, "ticks: %d", client->getGameTicks());
		geFontPrintfScreen(400, 0, client->res.font, 0xFFFFFFFF, "day: %d", client->getDayTicks());
		geFontPrintfScreen(0, 40, client->res.font, 0XFFFFFFFF, "Allocated memory: %d KBytes (%d blocks)", ((u32)geGetContext()->mem) / 1024, geGetContext()->allocs-geGetContext()->frees);
		geFontPrintfScreen(0, 80, client->res.font, 0XFFFFFFFF, "Allocated GPU memory: %d KBytes", ((u32)geGetContext()->gpumem) / 1024);
		geFontPrintfScreen(0, 120, client->res.font, 0xFFFFFFFF, "pos: %f %f %f", client->getPlayer()->getLocation().getX(), client->getPlayer()->getLocation().getY(), client->getPlayer()->getLocation().getZ());
		geFontPrintfScreen(0, 140, client->res.font, 0XFFFFFFFF, "Rendered chunks : %d/%d", nRchunks, client->getWorld()->getChunksCount());
		geFontPrintfScreen(0, 160, client->res.font, 0XFFFFFFFF, "Chunks update : %d", client->getThreadChunks()->getQueueSize());
		geFontPrintfScreen(0, 160, client->res.font, 0XFFFFFFFF, "                      %d", chunks_update.size());
		geFontPrintfScreen(0, 180, client->res.font, 0XFFFFFFFF, "View distance : %d / %d", client->options.curr_view_dist, client->options.view_distance);
		geFontPrintfScreen(0, 200, client->res.font, 0XFFFFFFFF, "Times (CPU/GPU) : %d / %d ms", cpu_time_p, gpu_time_p);
	}
	geDrawingMode(GE_DRAWING_MODE_3D);
	geSwapBuffers();
}

void ThreadRender::RenderSun(float* matrix){
	float sun_pos[4] = { 0.0, 0.0, 0.0, 1.0 };
	float sun_pos_2d[4] = { 0.0 };
	float* p_sun_pos_2d[1] = { sun_pos_2d };

	geMatrix44Vec4Mult(sun_pos_2d, matrix, sun_pos);
	sun_pos_2d[0] /= sun_pos_2d[3];
	sun_pos_2d[1] /= sun_pos_2d[3];
	sun_pos_2d[2] /= sun_pos_2d[3];

	geLightScatterRender(lightScatter, p_sun_pos_2d, 1);
	if(client->res.sun->position.z > 0.0){
		geGfxLensFlareRender(lensFlare, sun_pos_2d);
	}
}

void ThreadRender::RenderChunks(){
	float wind[2] = { ((float)geGetTick()) / 1000.0f, 0.0f };
	wind[0] = perlin_noise_2D(wind, 1, 1.0, 42);

	geShaderUse(client->res.shader_base);
	geShaderUniform1f(client->res.loc_wind, wind[0]);
	geShaderUniform1f(client->res.loc_global_illum, global_illum);

	geMatrixMode(GE_MATRIX_PROJECTION);
	geLoadIdentity();
	gePerspective(70.0, (float)geGetContext()->width/(float)geGetContext()->height, 0.05, 10000.0);

	geMatrixMode(GE_MATRIX_MODEL);
	geLoadIdentity();
	geUpdateMatrix();
	// One time is enough since all renderers use the same shader
	geRendererUpdate(client->getWorld()->getChunks()[0].getRenderer());

	std::list<Chunk*>* list = client->getThreadVisibility()->getVisibleChunks();
	if(!list){
		return;
	}
	nRchunksFaces = 0;
	nRchunks = list->size();
	for(std::list<Chunk*>::iterator it=list->begin(); it != list->end(); it++){
		Chunk* curr = *it;
		if(curr->getLod() >= 1){
			curr->getRenderer()->shader = client->res.shader_lod;
		}else{
			curr->getRenderer()->shader = client->res.shader_base;
		}
		geRendererUse(curr->getRenderer());
		geShaderUniform2f(client->res.loc_chunk_pos, curr->getX(), curr->getY());
		geDrawArray(GE_TRIANGLES, 0, curr->getRenderer()->nVerts);
	}
}

void ThreadRender::RenderPick(){
	float last_color[4] = { client->res.cube->verts[0].color[0], client->res.cube->verts[0].color[1], client->res.cube->verts[0].color[2], client->res.cube->verts[0].color[3] };
	int i;

	if(picking_block){
		client->res.render_pick->depth_mask = false;
		geRendererUse(client->res.render_pick);
		geMatrixMode(GE_MATRIX_MODEL);
		geLoadIdentity();
		geTranslate(picking_location->getX()+0.5, picking_location->getY()+0.5, picking_location->getZ()-0.005);
		geScale(1.01, 1.01, 1.01);
		geUpdateMatrix();
		geDrawArray(GE_TRIANGLES, 0, client->res.cube->nVerts);
		
		geLoadIdentity();
		geTranslate(picking_location->getX()+0.5, picking_location->getY()+0.5, picking_location->getZ()-0.005);
		geScale(1.03, 1.03, 1.03);
		geUpdateMatrix();
		for(i=0; i<client->res.cube->nVerts; i++){
			client->res.cube->verts[i].color[0] = 0.0;
			client->res.cube->verts[i].color[1] = 0.0;
			client->res.cube->verts[i].color[2] = 0.0;
			client->res.cube->verts[i].color[3] = 1.0;
		}
		geRendererUpdateContext(NULL, client->res.render_pick);
		geDrawArray(GE_LINE_STRIP, 0, client->res.cube->nVerts);
		for(i=0; i<client->res.cube->nVerts; i++){
			client->res.cube->verts[i].color[0] = last_color[0];
			client->res.cube->verts[i].color[1] = last_color[1];
			client->res.cube->verts[i].color[2] = last_color[2];
			client->res.cube->verts[i].color[3] = last_color[3];
		}
		geRendererUpdateContext(NULL, client->res.render_pick);
	}
}
